/* Server for Tic-Tac-Toe
 *
 * The server should mostly handle relay of moves. As stated in the
 * protocol all moves will be an even integer value. It will be the
 * servers job to remember which player is X and which O, and will
 * attach this to the movement command given by the client player. If
 * the server needs to interpret the next information it will recieve an
 * odd number as to what to expect. To make this easier COMMAND is
 * defined to 1.
 *
 * The server will also be the one check for the end of the game. To
 * make this easier all moves by a single player can be logical OR'ed
 * together, then this value can be checked against
 * WINNING_BYTE_CODE_[1-8]. Moves will be stored in an array of the Room
 * structure. By doing this, even index values will be moves by X and
 * odd ones by O (Might be useful to note).
 *
 * The games played, wins, losses should all be update by the server.
 *
 * See protocol.h for more detailed info.
 */
#include "server.h"

Player* players[MAX_CLIENTS];
Room** rooms;
int numRooms = 3;
int numClients;
fd_set rfd;

/* TODO: make this more generic */
void buildRooms(int num)
{
	rooms = malloc(num*sizeof(Room*));
	for(num--; 0 <= num; num--)
	{
		rooms[num] = calloc(1, sizeof(Room));
		rooms[num]->ID = num;
#define MAXRM 3
		rooms[num]->maxForRoom = MAXRM;
		rooms[num]->players = calloc(MAXRM, sizeof(Player*));
		rooms[num]->name = malloc(7);
		sprintf(rooms[num]->name, "Room %d", num);
		memset(rooms[num]->moves, 0, sizeof(rooms[num]->moves)/sizeof(int));
	}
}

/* Assigns a name to the player */
void readUserName(Player* ply)
{
	int len;
	char* oldName = NULL;
printf("Recieving new player name.\n");
	if(read(ply->ID, &len, sizeof(int)))
	{
		if(ply->name != NULL)
			oldName = ply->name;

		ply->name = malloc(len);
		if(!read(ply->ID, ply->name, len))
			connectionLost(ply);

		if(oldName == NULL) {
			printf("User %s logged in.\n", ply->name);
			sendPlayer(ply, ply);
		}else {
			printf("User %s changing name to %s.\n", oldName, ply->name);
			free(oldName);
		}
	}
	else
	  connectionLost(ply);
}


/* Recieve the and Room ID then send to the room players. */
void joinPlayerToRoom(Player* ply)
{
	int id, cmd, i;
	if(read(ply->ID, &id, sizeof(int)))
	{
		/* The room must first exist */
		if(id >= numRooms || id < 0) {
			cmd = KICK;
			write(ply->ID, &cmd, sizeof(int));
			write(ply->ID, &id, sizeof(int));
			return;
		}
		if(rooms[id]->numberInRoom < rooms[id]->maxForRoom)
		{
printf("The newest member of room %d is %s\n", rooms[id]->ID, ply->name);
			/* Add player to room */
			for(i = 0; i < rooms[id]->maxForRoom; i++)
				if(rooms[id]->players[i] == 0) {
					rooms[id]->players[i] = ply->ID;
					break;
				}

			cmd = ACCEPTED;

			/* State his acceptence */
			if(rooms[id]->numberInRoom == 0) {
				cmd = ADMIN;
				rooms[id]->playerX = ply->ID;
				rooms[id]->nextPlayer = ply->ID;
				memset(rooms[id]->moves, 0, sizeof(rooms[id]->moves));
			}else if(rooms[id]->numberInRoom == 1) {
				rooms[id]->playerO = ply->ID;
			}
			write(ply->ID, &cmd, sizeof(CODE));
			write(ply->ID, &rooms[id]->ID, sizeof(rooms[id]->ID));
			fsync(ply->ID);
			rooms[id]->numberInRoom++;

			/* Tell everone in the room of the new Player */
			for(i = 0; i < rooms[id]->maxForRoom; i++)
			{
				if(rooms[id]->players[i] == 0) continue;
				/* Make sure the client has the player */
				sendPlayer(getPlayer(rooms[id]->players[i]), ply);

				/* Tell the client to JOIN the player */
				cmd = JOIN;
				write(rooms[id]->players[i], &cmd, sizeof(CODE));
				cmd = rooms[id]->ID;
				write(rooms[id]->players[i], &cmd, sizeof(int));
				cmd = ply->ID;
				write(rooms[id]->players[i], &cmd, sizeof(int));
				fsync(rooms[id]->players[i]);
			}

		}
		else
		{
			cmd = SERVERFULL;
			write(ply->ID, &cmd, sizeof(CODE));
		}
		sendRoom(rooms[id], ply);
	}
	else
		connectionLost(ply);
}
void removePlayerFromRoom(Player* ply, int id) {
	int cmd, i;
	int plid = ply->ID;
printf("%s leaving room %d\n", ply->name, rooms[id]->ID);

	rooms[id]->numberInRoom--;

	/* Tell everone in the room Player has left */
	for(i = 0; i < rooms[id]->maxForRoom; i++)
	{
		if(rooms[id]->players[i] == 0)
			continue;
		if(rooms[id]->players[i] == ply->ID) {
			rooms[id]->players[i] = 0;
			continue;
		}
		cmd = LEAVE;
		write(rooms[id]->players[i], &cmd, sizeof(CODE));
		cmd = rooms[id]->ID;
		write(rooms[id]->players[i], &cmd, sizeof(int));
		cmd = ply->ID;
		write(rooms[id]->players[i], &cmd, sizeof(int));
		fsync(rooms[id]->players[i]);
	}

	if((plid == rooms[id]->playerX) || (plid == rooms[id]->playerO))
		kickAllFromRoom(id);
}

/* Player is no longer connected */
void playerQuit(Player* ply)
{
	int i, j;
	numClients--;
	FD_CLR(ply->ID, &rfd);
	close(ply->ID);

	/* Remove player from rooms */
	for(i = 0; i < numRooms; i++) {
		for(j = 0; j < rooms[i]->maxForRoom; j++)
			if(rooms[i] != NULL) {
				if(rooms[i]->players[j] == ply->ID) {
					removePlayerFromRoom(ply, i);
					if((rooms[i]->playerX == ply->ID) || (rooms[i]->playerO == ply->ID)) {
						kickAllFromRoom(i);
					}
				}
			}
	}


printf("Player");
		if(ply->name != NULL) {
printf(" %s", ply->name);
			free(ply->name);
			ply->name = NULL;
		}
printf(" has quit.\n");
	free(ply);
}

Player* getPlayer(int id) {
	int i;
	for(i = 0; i < MAX_CLIENTS; i++)
		if(players[i] != NULL)
		if(players[i]->ID == id)
			return players[i];
	return NULL;
}

void kickAllFromRoom(int r) {
printf("Kicking everone from room %d\n", r);
	int i, cmd = KICK;
	for(i = 0; i < rooms[r]->maxForRoom; i++) {
		if(rooms[r]->players[i] != 0) {
			write(rooms[r]->players[i], &cmd, sizeof(CODE));
			write(rooms[r]->players[i], &r, sizeof(int));
			rooms[r]->players[i] = 0;
		}
	}
	resetRoom(r);
}
/*
 * Random connection loss, must preform cleanup
 * Player must be removed from player array, and
 * any room that he resides.
 */
	/* TODO: */
void connectionLost(Player* ply)
{
	int i;
printf("Connection with client has died.\n");
	for(i = 0; i < MAX_CLIENTS; i++)
		if(players[i] != NULL)
			if(players[i]->ID == ply->ID) {
				players[i] = NULL;
			}
	playerQuit(ply);
}

void sendRoom(Room* rm, Player* ply)
{
	size_t len;
	int cmd = ROOM;
	int id = ply->ID;

	/* Hey, client, I'm sending you a room */
	write(id, &cmd, sizeof(cmd));
	write(id, &rm->ID, sizeof(rm->ID));

	/* length of room name */
	len = strlen(rm->name) + 1;
	write(id, &len, sizeof(len));

	write(id, rm->name, len);
	write(id, &rm->gamesPlayed, sizeof(rm->gamesPlayed));
	write(id, &rm->maxForRoom, sizeof(rm->maxForRoom));
	write(id, rm->players, rm->maxForRoom* sizeof(rm->players[0]));
	write(id, &rm->numberInRoom, sizeof(rm->numberInRoom));
	write(id, &rm->playerX, sizeof(rm->playerX));
	write(id, &rm->playerO, sizeof(rm->playerO));
	write(id, &rm->admin, sizeof(rm->admin));
	write(id, rm->moves, sizeof(rm->moves));
}

void sendRooms(Player* ply)
{
	int i;

	for (i = 0; i < numRooms; i++)
		sendRoom(rooms[i], ply);
}

void sendPlayer(Player* recv_ply, Player* ply)
{
	size_t len;
	int cmd = PLAYER;	
	int id = recv_ply->ID;

	/* Alert - about to send player info */
	write(id, &cmd, sizeof(cmd));
	write(id, &ply->ID, sizeof(ply->ID));

	/* send length of name */
	len = strlen(ply->name) + 1;
	write(id, &len, sizeof(len));

	write(id, ply->name, len);
	write(id, &ply->gamesPlayed, sizeof(ply->gamesPlayed));
	write(id, &ply->wins, sizeof(ply->wins));
	write(id, &ply->losses, sizeof(ply->losses));
}

void sendPlayers(Player* ply)
{
	int i, count;
	for (i = 0, count = 0;
		i < MAX_CLIENTS && count < numClients - 1;
		i++)
	{
		if (players[i] != NULL && players[i]->ID != ply->ID)
		{
			sendPlayer(ply, players[i]);
			count++;
		}
	}
}

void sendMove(CODE cmd, int r) {
	int i;
	for(i = 0; i < sizeof(rooms[i]->moves); i++) {
		if(rooms[r]->moves[i] == 0) {
			rooms[r]->moves[i] = cmd;
			break;
		}
	}
	for(i = 0; i < rooms[r]->numberInRoom; i++) {
		int plid = rooms[r]->players[i];
		write(plid, &cmd, sizeof(CODE));
		write(plid, &r, sizeof(int));
	}
}

void sendWinner(int winner, int r) {
	int i, cmd = WINNER;

	for(i = 0; i < rooms[r]->maxForRoom; i++) {
		int plid = rooms[r]->players[i];
		if(plid == 0)
			continue;
		write(plid, &cmd, sizeof(CODE));
		write(plid, &r, sizeof(int));
		if(winner == X)
			write(plid, &rooms[r]->playerX, sizeof(int));
		else if(winner == O)
			write(plid, &rooms[r]->playerO, sizeof(int));
		else
			write(plid, &winner, sizeof(int));
		write(plid, &winner, sizeof(int));
	}
}

int played(int move, int r) {
	int i;
	for(i = 0; i < MOVES_IN_A_GAME; i++)
		if(rooms[r]->moves[i] & move)
			return 1;
	return 0;
}

int checkForWinner(int Xmoves, int Omoves) {
	if(((Xmoves & WINNING_BYTE_CODE_1) == WINNING_BYTE_CODE_1) ||
		((Xmoves & WINNING_BYTE_CODE_2) == WINNING_BYTE_CODE_2) ||
		((Xmoves & WINNING_BYTE_CODE_3) == WINNING_BYTE_CODE_3) ||
		((Xmoves & WINNING_BYTE_CODE_4) == WINNING_BYTE_CODE_4) ||
		((Xmoves & WINNING_BYTE_CODE_5) == WINNING_BYTE_CODE_5) ||
		((Xmoves & WINNING_BYTE_CODE_6) == WINNING_BYTE_CODE_6) ||
		((Xmoves & WINNING_BYTE_CODE_7) == WINNING_BYTE_CODE_7) ||
		((Xmoves & WINNING_BYTE_CODE_8) == WINNING_BYTE_CODE_8)) {
			return X;
		}
	if(((Omoves & WINNING_BYTE_CODE_1) == WINNING_BYTE_CODE_1) ||
		((Omoves & WINNING_BYTE_CODE_2) == WINNING_BYTE_CODE_2) ||
		((Omoves & WINNING_BYTE_CODE_3) == WINNING_BYTE_CODE_3) ||
		((Omoves & WINNING_BYTE_CODE_4) == WINNING_BYTE_CODE_4) ||
		((Omoves & WINNING_BYTE_CODE_5) == WINNING_BYTE_CODE_5) ||
		((Omoves & WINNING_BYTE_CODE_6) == WINNING_BYTE_CODE_6) ||
		((Omoves & WINNING_BYTE_CODE_7) == WINNING_BYTE_CODE_7) ||
		((Omoves & WINNING_BYTE_CODE_8) == WINNING_BYTE_CODE_8)) {
			return O;
		}

	return 0;
}

void resetRoom(int r) {
	rooms[r]->numberInRoom = 0;
	rooms[r]->playerX = 0;
	rooms[r]->playerO = 0;
	rooms[r]->nextPlayer = 0;
	rooms[r]->admin = 0;
	memset(rooms[r]->moves, 0, sizeof(rooms[r]->moves));
}

/* 
 * Client will be sending a room ID and two player's IDs of that room.
 * The player with the lowest wins/played ration will be chosen to be X.
 */
void setupGame(Player* ply)
{
	int one, two, room;

	//read(
}
